# ! 应当在blender内以脚本形式运行
# 真是个石山

import bpy
import bpy_extras
import random
import cv2
import time
from mathutils import Vector
from mathutils import Euler
import xml.etree.ElementTree as ET
from xml.dom import minidom

PI = 3.14159

workspace_scene_name = "scene3"
workspace_scene = bpy.data.scenes.get(workspace_scene_name)

CMENTIMETRE = "centimetre"
METER = "meter"

def randMoveObj(obj, x_range, y_range, z_range):
    if isinstance(obj, bpy.types.Object):
        target_obj = obj
    elif isinstance(obj, str):
        target_obj = workspace_scene.objects.get(obj)
        if not target_obj:
            raise ValueError(f"Object '{obj}' not found in the scene.")
    else:
        raise TypeError()

    if obj:
        # 生成随机位置
        random_x = random.uniform(x_range[0], x_range[1])
        random_y = random.uniform(y_range[0], y_range[1])
        random_z = random.uniform(z_range[0], z_range[1])
        
        # 设置物体位置
        obj.location = (random_x, random_y, random_z)


def rotateObject(obj, z_min=0, z_max=2*PI):
    if isinstance(obj, bpy.types.Object):
        target_obj = obj
    elif isinstance(obj, str):
        target_obj = workspace_scene.objects.get(obj)
        if not target_obj:
            raise ValueError(f"Object '{obj}' not found in the scene.")
    else:
        raise TypeError()

    obj_rotation = target_obj.rotation_euler

    # 生成随机的旋转角度 (弧度范围: 0 到 2π)
    target_z_rotation = random.uniform(z_min, z_max)  # 随机 Z 轴旋转

    # 将随机角度设置为物体的旋转
    target_obj.rotation_euler = Euler((obj_rotation.x, obj_rotation.y, target_z_rotation), 'XYZ')


# 模拟重力放置物体
def placeObjectWithG(obj, x, y, z_min, z_max, z_offset = 0, unit = CMENTIMETRE):
    """
    模拟重力放置物体
    在z轴上从z_max往z_min找, 是否有其他物体
    如果有则设定obj的z坐标设定为在其上方, 也就是将物体简单模拟重力情况下的放置/物体不悬空
    如果找不到下面的物体(地表)就随机生成z值
    """
    if unit == CMENTIMETRE:
        x = x / 100
        y = y / 100
        z_min = z_min / 100
        z_max = z_max / 100
        z_offset = z_offset / 100
    elif unit == METER:
        pass
    else:
        raise TypeError("unknown unit")

    if isinstance(obj, bpy.types.Object):
        target_obj = obj
    elif isinstance(obj, str):
        target_obj = workspace_scene.objects.get(obj)
        if not target_obj:
            raise ValueError(f"Object '{obj}' not found in the scene.")
    else:
        raise TypeError()

    # obj_bbox = [target_obj.matrix_world @ Vector(corner) for corner in target_obj.bound_box]
    # obj_bbox_x_range = [v.x for v in obj_bbox]
    # obj_bbox_y_range = [v.y for v in obj_bbox]
    # obj_bbox_x_min = min(obj_bbox_x_range) - target_obj.location.x + x
    # obj_bbox_x_max = max(obj_bbox_x_range) - target_obj.location.x + x
    # obj_bbox_y_min = min(obj_bbox_y_range) - target_obj.location.y + y
    # obj_bbox_y_max = max(obj_bbox_y_range) - target_obj.location.y + y

    # 搜索从 z_max 到 z_min 是否有物体
    target_z = None
    ignore_objects = ["Camera.s3", "小提灯.s3.001", "高天棚灯.s3.001"]
    for test_obj in workspace_scene.objects:
        if test_obj == target_obj:  # 跳过目标物体本身
            continue
        if (abs(test_obj.location.x) > 5) or (abs(test_obj.location.y) > 5): # 认为参与碰撞的物体的坐标只在 x+-5m y+-5m内
            continue
        if test_obj.name in ignore_objects:
            continue

        # 获取测试物体的边界盒（包围盒）
        test_obj_point3d_cloud = [test_obj.matrix_world @ Vector(corner) for corner in test_obj.bound_box]

        # 检查测试物体是否在目标 x, y 坐标的投影范围内
        test_obj_point3d_x_range = [v.x for v in test_obj_point3d_cloud]
        test_obj_point3d_y_range = [v.y for v in test_obj_point3d_cloud]
        test_obj_point3d_x_min = min(test_obj_point3d_x_range)
        test_obj_point3d_x_max = max(test_obj_point3d_x_range)
        test_obj_point3d_y_min = min(test_obj_point3d_y_range)
        test_obj_point3d_y_max = max(test_obj_point3d_y_range)
        if not (test_obj_point3d_x_min <= x <= test_obj_point3d_x_max and test_obj_point3d_y_min <= y <= test_obj_point3d_y_max):
            continue

        # if ( \
        #     obj_bbox_x_max < test_obj_bbox_x_min or test_obj_bbox_x_max < obj_bbox_x_min or \
        #     obj_bbox_y_max < test_obj_bbox_y_min or test_obj_bbox_y_max < obj_bbox_y_min \
        # ):
        #     continue

        # test_obj_z_min = min([v.z for v in bbox])
        test_obj_z_max = max([v.z for v in test_obj_point3d_cloud])

        if z_min <= test_obj_z_max <= z_max:  # z_max_test 在搜索范围内
            target_z = max(target_z, test_obj_z_max) if target_z else test_obj_z_max

    # 如果找到物体，将其放置在物体上方，否则随机生成 z 值
    if target_z is None:
        target_obj.location.z = random.uniform(z_min, z_max)
    else:
        target_obj.location.z = target_z + z_offset
    target_obj.location.x = x
    target_obj.location.y = y
    bpy.context.view_layer.update()


def renderPhoto(camera_name, output_path, img_height=480, img_width=640, file_format="PNG"):
    # scene = bpy.context.scene
    scene = workspace_scene

    camera = bpy.data.objects.get(camera_name)
    if not camera:
        raise ValueError(f"Camera '{camera_name}' not found in the scene.")
    
    # 设置活动摄像机
    scene.camera = camera

    # 参数
    scene.render.filepath = output_path
    scene.render.resolution_x = img_width
    scene.render.resolution_y = img_height
    scene.render.image_settings.file_format = file_format

    # 渲染
    bpy.ops.render.render(write_still=True)


def calBox(obj, camera, blocking_thr: float):
    if isinstance(obj, bpy.types.Object):
        target_obj = obj
    elif isinstance(obj, str):
        target_obj = workspace_scene.objects.get(obj)
        if not target_obj:
            raise ValueError(f"Object '{obj}' not found in the scene.")
    else:
        raise TypeError()

    if isinstance(camera, bpy.types.Object):
        target_camera = camera
    elif isinstance(camera, str):
        target_camera = workspace_scene.objects.get(camera)
        if not target_camera:
            raise ValueError(f"Object '{obj}' not found in the scene.")
    else:
        raise TypeError()

    camera_loc = target_camera.location
    obj_loc = target_obj.location
    obj_loc_camera_coords = bpy_extras.object_utils.world_to_camera_view(bpy.context.scene, target_camera, obj_loc)

    # 如果物体坐标超出画幅
    if  obj_loc_camera_coords.x < 0 or obj_loc_camera_coords.x > 1 or \
        obj_loc_camera_coords.y < 0 or obj_loc_camera_coords.y > 1 or \
        obj_loc_camera_coords.z < 0:
        return None

    obj_point3d_cloud = [target_obj.matrix_world @ vert.co for vert in target_obj.data.vertices]
    obj_point2d_camera_coords = [ \
        bpy_extras.object_utils.world_to_camera_view(bpy.context.scene, target_camera, point) \
        for point in obj_point3d_cloud \
    ]

    unblock_rate = 1
    if blocking_thr > 0:
        all_point_number = 0
        unblocked_point_number = 0

        for point in obj_point3d_cloud:
            direction = point - camera_loc
            direction.normalize()
            result, location, normal, index, hit_object, matrix = bpy.context.scene.ray_cast(
                bpy.context.view_layer.depsgraph, camera_loc, direction
            )

            all_point_number += 1
            if result and (hit_object == target_obj):
                unblocked_point_number += 1

        unblock_rate = unblocked_point_number / all_point_number

    xs = [point.x for point in obj_point2d_camera_coords]
    ys = [point.y for point in obj_point2d_camera_coords]
    x_max, x_min = max(xs), min(xs)
    y_max, y_min = max(ys), min(ys)

    return x_min, x_max, y_min, y_max, round(unblock_rate, 2)


def createDetectionObjectsList(objects_list: list, camera_name: str, img_height=480, img_width=640) -> list:
    """
    :param blocking_thr: 遮挡关系阈值, 遮挡比例低于 blocking_thr 的物体将被舍弃
    """

    objects = []
    for obj_data in objects_list:
        obj_name = obj_data["name"]
        obj_type = obj_data["type"]
        blocking_thr = obj_data["blocking_rate_min"]
        box = calBox(obj_name, camera_name, blocking_thr)
        if box == None:
            continue

        x_min, x_max, y_min, y_max, unblock_rate = box

        print(f"find {obj_name}, unblock_rate: {unblock_rate}")

        if unblock_rate <= blocking_thr:
            continue

        x_min = int(x_min * img_width)
        x_max = int(x_max * img_width)
        y_min = int((1 - y_min) * img_height)
        y_max = int((1 - y_max) * img_height)

        y_min, y_max = y_max, y_min

        x_min = max(0, min(img_width - 1, x_min))
        x_max = max(0, min(img_width - 1, x_max))
        y_min = max(0, min(img_width - 1, y_min))
        y_max = max(0, min(img_width - 1, y_max))

        obj_dict = {"name": obj_type, "difficult": 0, "bbox": [x_min, y_min, x_max, y_max]}
        print(f"append {obj_name}, data: {obj_dict}")
        objects.append(obj_dict)

    return objects


def createAnnotationText(img_name: str, img_size: dict, objects: list) -> str:
    """
    生成目标检测标注的 XML 文件。
    
    :param img_name: 图像文件名, "block1.jpg"
    :param img_size: 图像大小, {"width": 宽度, "height": 高度}
    :param objects: [
        {"name": 类别名, "difficult": 0/1, "bbox": [xmin, ymin, xmax, ymax]},
        {"name": 类别名, "difficult": 0/1, "bbox": [xmin, ymin, xmax, ymax]},
        ...
    ]
    :return: XML 字符串
    """

    """
    annotation
    |- filename
    |- object_num
    |- size
    |  |- width
    |  |- height
    |
    |- object
    |  |- name
    |  |- difficult
    |  |- bndbox
    |  |  |- xmin
    |  |  |- ymin
    |  |  |- xmax
    |  |  |- ymax
    |
    |- object
    |  |- ...
    |
    """

    # <annotation>
    root_tag = "annotation"
    annotation = ET.Element(root_tag)
    
    # <filename>
    filename_tag = ET.SubElement(annotation, "filename")
    filename_tag.text = img_name
    
    # <object_num>
    object_num_tag = ET.SubElement(annotation, "object_num")
    object_num_tag.text = str(len(objects))

    # <size>
    size_tag = ET.SubElement(annotation, "size")
    width_tag = ET.SubElement(size_tag, "width")
    width_tag.text = str(img_size["width"])
    height_tag = ET.SubElement(size_tag, "height")
    height_tag.text = str(img_size["height"])
    
    # <object>
    for obj in objects:
        object_tag = ET.SubElement(annotation, "object")
        
        name_tag = ET.SubElement(object_tag, "name")
        name_tag.text = obj["name"]
        
        difficult_tag = ET.SubElement(object_tag, "difficult")
        difficult_tag.text = str(obj["difficult"])
        
        bndbox_tag = ET.SubElement(object_tag, "bndbox")
        xmin_tag = ET.SubElement(bndbox_tag, "xmin")
        xmin_tag.text = str(obj["bbox"][0])
        ymin_tag = ET.SubElement(bndbox_tag, "ymin")
        ymin_tag.text = str(obj["bbox"][1])
        xmax_tag = ET.SubElement(bndbox_tag, "xmax")
        xmax_tag.text = str(obj["bbox"][2])
        ymax_tag = ET.SubElement(bndbox_tag, "ymax")
        ymax_tag.text = str(obj["bbox"][3])
    
    # 转换为字符串
    xml_content = ET.tostring(annotation, encoding="unicode")
    # 缩进
    xml_content = minidom.parseString(xml_content).toprettyxml(indent="  ")
    return xml_content


def saveAnnotationXmlFile(xml_file_name, content):
    """
    保存生成的 XML 内容到文件。
    
    :param xml_file_name: 保存的文件路径
    :param content: XML 内容字符串
    """
    with open(xml_file_name, "w", encoding="utf-8") as f:
        f.write(content)


def createRandomPoints(range_list: list, distance_min: int) -> list:
    """
    range_list: [
        {   "random_range": {"x": [-30, 0], "y":[-30, 30]}, 
            "points_max": 10
        },
        {   "random_range": {"x": [0, 30], "y":[-30, 30]}, 
            "points_max": 15
        },
    ]
    """
    points_list = []
    existing_points = set()
    for data in range_list:
        x_range = [int(x / distance_min + 0.5) for x in data["random_range"]["x"]]
        y_range = [int(y / distance_min + 0.5) for y in data["random_range"]["y"]]

        i = 0
        overtime_check = 0
        while (i < data["points_max"]) and (overtime_check < 5 * data["points_max"]):
            x = random.randint(x_range[0], x_range[1]) * distance_min
            y = random.randint(y_range[0], y_range[1]) * distance_min
            point = (x, y)
            if point not in existing_points:
                existing_points.add(point)
                points_list.append([x, y])
                i += 1
            overtime_check += 1

    return points_list


# ===================================================================================================

DITTO = "ditto"

# Camera at (0, -40, 25), (45d, 0, 0)
"""
物体会按照random_objects_data内的编写顺序被随机放置
[{第一个被尝试放置}, [{第二个被尝试放置}, ...]

{   "name": 物件名称, 
    "type": 生成标注文件内这个物体的所属类型(没有的话填写"None"), 
    "detection": 是否需要被标注(是被画框物体还是杂质), 
    "blocking_rate_min": 标注物体(如果是)所需的最低遮挡率(0.6代表摄像头视角内看到的物体有>=60%的顶点未被其他物体挡住时才被标注),
    "random_range": 物体的随机坐标范围,
    "z_rotation": 物体的随机旋转范围(弧度制, 已定义PI代表180度),
    "gravity": 是否启用重力(如果启用, 会将random_range的z轴的随机范围作为搜索范围, 找到低下的物体后会将新物体叠在它上面), 
    "place_odds": 放置概率(为0就代表不放置)
}
"""
random_objects_data = [
    {   "name": "L100.s3.001", "type": "None", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-60, 60], "y":[-1, 1], "z": [-0.7, -0.7]}, "z_rotation": {"min": 0, "max": PI},
        "gravity": False, "place_odds": 0.5
    },
    {   "name": "R50-360.s3.001", "type": "None", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-60, 60], "y":[-1, 1], "z": [-0.8, -0.8]}, "z_rotation": {"min": 0, "max": PI},
        "gravity": False, "place_odds": 0.5
    },
    {   "name": "板砖.s3.001", "type": "block", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-30, 30], "y":[-30, 30], "z": [-1, 20]}, "z_rotation": {"min": 0, "max": 2*PI},
        "gravity": True, "place_odds": 0.1
    },
    {   "name": "小提灯.s3.001", "type": "None", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-30, 30], "y":[-30, 30], "z": [80, 120]}, "z_rotation": {"min": 0, "max": 2*PI},
        "gravity": False, "place_odds": 1.0
    },

    {   "name": "立方体.s3.001", "type": "立方体", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-30, 30], "y":[-40, 0], "z": [-1, 20]}, "z_rotation": {"min": 0, "max": 2*PI},
        "gravity": True, "place_odds": 0.2
    },
    {   "name": "立方体.s3.002", "type": "立方体", "detection": False, "blocking_rate_min": 0,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": True, "place_odds": 0.05
    },
    {   "name": "立方体.s3.003", "type": "立方体", "detection": False, "blocking_rate_min": 0,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": True, "place_odds": 0.05
    },

    {   "name": "黄色锥桶.s3.001", "type": "cone", "detection": False, "blocking_rate_min": 0.5,
        "random_range": {"x": [-25, 25], "y":[-36, 0], "z": [-1, 100]}, "z_rotation": False,
        "gravity": True, "place_odds": 0.3
    },
    {   "name": "黄色锥桶.s3.002", "type": "cone", "detection": False, "blocking_rate_min": 0.5,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": DITTO, "place_odds": 0.3
    },

    {   "name": "I车模.s3.001", "type": "None", "detection": False, "blocking_rate_min": 0,
        "random_range": {"x": [-45, 45], "y":[0, 80], "z": [-1, 100]}, "z_rotation": {"min": 0, "max": 2*PI},
        "gravity": True, "place_odds": 0
    },

    {   "name": "坡道标志.s3.001", "type": "bridge", "detection": True, "blocking_rate_min": 0.6,
        "random_range": {"x": [-32, 32], "y":[-12, 12], "z": [-1, 100]}, "z_rotation": {"min": -PI/30, "max": PI/30},
        "gravity": True, "place_odds": 0.6
    },
    {   "name": "汉堡0.s3.001", "type": "burger", "detection": True, "blocking_rate_min": 0.6,
        "random_range": {"x": [-32, 32], "y":[0, 12], "z": [-1, 100]}, "z_rotation": {"min": 0, "max": PI/2},
        "gravity": True, "place_odds": 0.6
    },
    {   "name": "汉堡0.s3.002", "type": "burger", "detection": True, "blocking_rate_min": 0.6,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": DITTO, "place_odds": 0.6
    },
    {   "name": "标志-充电.s3.001", "type": "battery", "detection": True, "blocking_rate_min": 0.35,
        "random_range": {"x": [-32, 32], "y":[0, 12], "z": [5, 12]}, "z_rotation": {"min": -PI/12, "max": PI/12},
        "gravity": True, "place_odds": 0.6
    },
    {   "name": "标志-公文包.s3.001", "type": "company", "detection": True, "blocking_rate_min": 0.35,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": DITTO, "place_odds": 0.6
    },
    {   "name": "标志-大学.s3.001", "type": "school", "detection": True, "blocking_rate_min": 0.35,
        "random_range": DITTO, "z_rotation": DITTO,
        "gravity": DITTO, "place_odds": 0.6
    }
]

# z轴坐标偏移, 物体最终放置的坐标会加上这个值
z_offsets_map = {
    "None": 0, "立方体": 6, "cone": 3.82, "block": 2.65, "bridge": 1, "burger": 0, 
    "battery": 5, "company": 5, "school": 5
}

def randomProcess2():

    base_xyz = (10, 0, 0)
    for obj_data in random_objects_data:
        obj = workspace_scene.objects.get(obj_data["name"])
        obj.location = base_xyz

    # time.sleep(0.01)

    # rotateObject("小提灯.s3.001", PI/2, 1.5*PI)
    light = workspace_scene.objects.get("高天棚灯.s3.001")
    light.location = (random.uniform(-2, 2), random.uniform(-1, 1), 10)
    # light = workspace_scene.objects.get("小提灯.s3.001")
    # light.location.x = random.uniform(-0.1, -1.5)

    random_range = {"x": [0, 0], "y":[0, 0], "z": [0, 0]}
    z_rotation = {"min": 0, "max": 2*PI},
    gravity = True
    for obj_data in random_objects_data:
        obj_name = obj_data["name"]
        obj_type = obj_data["type"]
        if obj_data["random_range"] != DITTO:
            random_range = obj_data["random_range"]
        if obj_data["z_rotation"] != DITTO:
            z_rotation = obj_data["z_rotation"]
        if obj_data["gravity"] != DITTO:
            gravity = obj_data["gravity"]

        if random.random() >= obj_data["place_odds"]:
            continue

        if random_range != False:
            x = random.uniform(random_range["x"][0], random_range["x"][1])
            y = random.uniform(random_range["y"][0], random_range["y"][1])

            if gravity != False:
                placeObjectWithG(obj_name, x, y, random_range["z"][0], random_range["z"][1], z_offsets_map[obj_type])
            else:
                z = random.uniform(random_range["z"][0], random_range["z"][1])
                target_obj = workspace_scene.objects.get(obj_name)
                target_obj.location = (x/100, y/100, z/100)

        if z_rotation != False:
            rotateObject(obj_name, z_rotation["min"], z_rotation["max"])
        bpy.context.view_layer.update()


def randomProcess3():
    base_xyz = (10, 0, 0)
    for obj_data in random_objects_data:
        obj = workspace_scene.objects.get(obj_data["name"])
        obj.location = base_xyz

    light = workspace_scene.objects.get("高天棚灯.s3.001")
    light.location = (random.uniform(-2, 2), random.uniform(-1, 1), 15)

    points_random_range = [
        {"random_range": {"x": [-20, 20], "y":[-32, -15]}, "points_max": 4},
        {"random_range": {"x": [-35, 35], "y":[-15, 10]}, "points_max": 6},
        {"random_range": {"x": [-50, 50], "y":[10, 60]}, "points_max": 6},
    ]

    points_list = createRandomPoints(points_random_range, 5)
    random.shuffle(points_list)

    i = 0
    random_range = {"x": [0, 0], "y":[0, 0], "z": [0, 0]}
    z_rotation = {"min": 0, "max": 2*PI},
    gravity = True
    for obj_data in random_objects_data:
        obj_name = obj_data["name"]
        obj_type = obj_data["type"]
        if obj_data["random_range"] != DITTO:
            random_range = obj_data["random_range"]
        if obj_data["z_rotation"] != DITTO:
            z_rotation = obj_data["z_rotation"]
        if obj_data["gravity"] != DITTO:
            gravity = obj_data["gravity"]

        i += 1
        if i >= len(points_list):
            break
        if random.random() >= obj_data["place_odds"]:
            continue

        if gravity != False:
            placeObjectWithG(obj_name, 
                points_list[i][0], points_list[i][1], random_range["z"][0], random_range["z"][1], z_offsets_map[obj_type]
            )
        else:
            z = random.uniform(random_range["z"][0], random_range["z"][1])
            target_obj = workspace_scene.objects.get(obj_name)
            target_obj.location = (points_list[i][0]/100, points_list[i][1]/100, z/100)

        if isinstance(z_rotation, dict):
            rotateObject(obj_name, z_rotation["min"], z_rotation["max"])
        bpy.context.view_layer.update()


def main2():
    print("run")

    root_path = "C:/Users/YU/Desktop/建模/数据生成/"

    for i in range(660, 700):
        time.sleep(0.01)
        print(f"now at {i}")
        randomProcess2()

        detection_obj_list = [obj for obj in random_objects_data if obj["detection"] == True]
        objects_data = createDetectionObjectsList(detection_obj_list, "Camera.s3")

        img_name = f"img{i}.jpg"
        xml_name = f"img{i}.xml"

        renderPhoto("Camera.s3", root_path + "img/" + img_name)

        img_size = {"width": 640, "height": 480}
        xml_content = createAnnotationText(img_name, img_size, objects_data)
        saveAnnotationXmlFile(root_path + "ann/" + xml_name, xml_content)


        image = cv2.imread(root_path + "img/" + img_name)

        if image is None:
            print("无法读取图像")
        else:
            for obj in objects_data:
                start_point = (obj["bbox"][0], obj["bbox"][3])
                end_point = (obj["bbox"][2], obj["bbox"][1])

                color = (0, 255, 0)
                thickness = 1

                cv2.rectangle(image, start_point, end_point, color, thickness)

            output_path = root_path + f"box/box{i}.png"
            cv2.imwrite(output_path, image)

def main3():
    print("run")

    root_path = "C:/Users/YU/Desktop/建模/数据生成/"

    for i in range(0, 50):
        print(f"now at {i}")
        img_size = {"width": 320, "height": 240}
        randomProcess3()

        detection_obj_list = [obj for obj in random_objects_data if obj["detection"] == True]
        objects_data = createDetectionObjectsList(detection_obj_list, "Camera.s3", img_size["height"], img_size["width"])

        img_name = f"img{i}.png"
        xml_name = f"img{i}.xml"

        img_path = root_path + "img/" + img_name

        renderPhoto("Camera.s3", img_path, img_size["height"], img_size["width"])

        xml_content = createAnnotationText(img_name, img_size, objects_data)
        saveAnnotationXmlFile(root_path + "ann/" + xml_name, xml_content)

        image = cv2.imread(img_path)

        if image is None:
            print("无法读取图像")
        else:
            for obj in objects_data:
                start_point = (obj["bbox"][0], obj["bbox"][3])
                end_point = (obj["bbox"][2], obj["bbox"][1])

                color = (0, 255, 0)
                thickness = 1

                cv2.rectangle(image, start_point, end_point, color, thickness)

            output_path = root_path + f"box/box{i}.png"
            cv2.imwrite(output_path, image)

main3()